/**
 * @description: 银行取钱问题小案例
 * 创建实体类Account
 * 再建一个银行类实现Runnable接口，可以对账户进行操作：取钱，减余额操作
 * 启动两个线程对账户余额同时操作
 * @author: zys
 * @create: 2020-10-11 19:45
 **/
public class TestSynchronized {

    /**
     * Synchronized在1.6经历各种优化，变得不在那么的重
     * 普通方法锁的是当前实例对象
     * 静态方法，锁的当前类是Class对象
     * 同步方法块，锁的是括号里的对象
     * 每个对象有一个监视器锁（monitor）
     * monitorenter  monitorexit
     */

    //创建实体类Account，账户类，只有一个属性：余额
    static class Account {
        private double balance;

        public Account(double balance) {
            this.balance = balance;
        }

        public void setBalance(double balance) {
            this.balance = balance;
        }

        public double getBalance() {
            return this.balance;
        }
    }

    //再建一个银行类实现Runnable接口，可以对账户进行操作：取钱，减余额操作
    static class Bank implements Runnable {
        private Account account;
        private double money;

        public Bank(Account account, double money) {
            this.account = account;
            this.money = money;
        }

        @Override
        public void run() {
            drawMoney();
        }

        public void drawMoney() {
            while (account.getBalance() >= money) {
                synchronized (this) {
                    if (account.getBalance() >= money) {
                        double balance = account.getBalance() - money;
                        System.out.println(Thread.currentThread().getName()
                                + "余额:" + account.getBalance() + ",取钱:" + money + ",剩余:" + balance);
                        account.setBalance(balance);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("余额不足");
                    }
                }
            }
        }
    }


    public static void main(String[] args) {
        //启动两个线程对账户余额同时操作
        Account account = new Account(2000);
        Bank bank = new Bank(account, 300);
        Thread thread1 = new Thread(bank, "A-->");
        Thread thread2 = new Thread(bank, "B-->");
        /**
         * 低优先级：1~4，其中类变量Thread.MIN_PRORITY最低，数值为1；
         * 默认优先级：如果一个线程没有指定优先级，默认优先级为5，由类变量Thread.NORM_PRORITY表示；
         * 高优先级：6~10，类变量Thread.MAX_PRORITY最高，数值为10。*/
//        thread1.setPriority(1);
//        thread2.setPriority(10);
        thread1.start();
        thread2.start();
    }

    /**
     * 总结
     * 1 不做任何处理，当A、B线程同时执行到1时，都读取到余额800，A线程先执行-300操作，剩余500，此时，切换线程到B，
     * 刚刚B线程读取到的是800，-300，剩余500，这样，就出现了图中所示问题，实际取到了600块钱，余额却只减了300，
     *
     * 2 同步方法一旦执行，就独占该锁，直到从该方法返回时才将锁释放，此后被阻塞的线程方能获得该锁，所以，在线程就绪状态时，
     * A、B两个线程都有机会获得CPU时间片执行，一旦其中一个线程先执行，那么就独占该锁，直到public synchronized void
     * drawMoney2()方法执行完毕，另一线程才有机会执行，当drawMoney2执行完时，只剩200块钱了，小于300，B线程执行不了方法内部的代码，
     * 也就没有任何输出，但实际上，B线程也是执行了drawMoney2方法的运行结果只有A或者B才能取到钱，可以证明synchronized
     * 同步方法将会锁住整个方法的整段代码，运行期间其他线程无法拿到对象锁，会被阻塞，这样效率不是很高
     *
     *  3 可以看到，A、B线程并发操作，并且没有出现账户余额差错的情况，这是由于，同步块被放在while循环判断内部，
     *  而同步块锁住的范围比同步方法小，只会锁住synchronized(syncObject) {}大括号中的代码，专业点就是加锁粒度比同步方法小，
     *  当一次循环结束后，另外一个线程有机会竞争资源执行synchronized代码块中代码，对比（1）中同步方法写法，
     *  while循环取钱操作被放在synchronized同步方法中，一旦一个线程获得对象锁，执行同步方法，就会执行完while循环中的代码，
     *  所以会一直取钱，直到条件不满足，退出循环，释放锁
     *
     * */


}